/*
    Copyright (C) 2009, 2010, 2011, 2012 Daniel Gutson, Martin Gioiosa,
    Lucas Paradisi, Leonardo Boquillon and Hugo Arregui, FuDePAN

    This file is part of Biopp.

    Aso is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Biopp is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Biopp.  If not, see <http://www.gnu.org/licenses/>.

    NOTE: This file is in prototype stage, and is under active development.
*/

#ifndef BIO_MOLECULAR_INLINE_H
#error Internal header file, DO NOT include this.
#endif

#include <sstream>

namespace biopp
{

template<class TripletClass>
SequenceBase<TripletClass>::SequenceBase(const std::string& str)
    : offset(0)
{
    const size_t size = str.size();
    if (!str.empty())
    {
        seq.reserve(size);
        for (size_t i = 0; i < size; i++)
        {
            insert_into(seq, TripletClass::convertNucTypeFromChar(str[i]));
        }
    }
}

template <class TripletClass>
size_t SequenceBase<TripletClass>::triplets_length() const
{
    if (seq.size() < offset)
        throw InvalidOffset();

    return (seq.size() - offset) / 3;
}

template <class TripletClass>
SequenceBase<TripletClass>& SequenceBase<TripletClass>::operator=(const SequenceBase<TripletClass>& other)
{
    seq = other.seq;
    offset = other.offset;
    return *this;
}

template <class TripletClass>
bool SequenceBase<TripletClass>::operator==(const SequenceBase<TripletClass>& other) const
{
    bool equal = (this->length() == other.length());
    if (equal)
    {
        equal = std::equal(seq.begin() + offset, seq.end(), other.seq.begin() + other.offset);
    }
    return equal;
}

template <class TripletClass>
bool SequenceBase<TripletClass>::operator!=(const SequenceBase<TripletClass>& other) const
{
    return !operator ==(other);
}

template <class TripletClass>
SequenceBase<TripletClass>& SequenceBase<TripletClass>::operator+=(typename TripletClass::NucType nucl)
{
    seq.push_back(nucl);
    return *this;
}

template <class TripletClass>
size_t SequenceBase<TripletClass>::calcTripletPosition(size_t i) const
{
    return i * 3 + offset;
}

template <class TripletClass>
typename TripletClass::NucType SequenceBase<TripletClass>::operator[](size_t idx) const
{
    return seq[idx + offset];
}

template <class TripletClass>
typename TripletClass::NucType& SequenceBase<TripletClass>::operator[](size_t idx)
{
    return seq[idx + offset];
}

template <class TripletClass>
void SequenceBase<TripletClass>::clear()
{
    offset = 0;
    seq.clear();
}

template <class TripletClass>
void SequenceBase<TripletClass>::set_offset(size_t new_offset)
{

    if (new_offset > seq.size() - 1 || new_offset > 2)
    {
        throw InvalidOffset();
    }
    else
    {
        offset = new_offset;
    }
}

template <class TripletClass>
size_t SequenceBase<TripletClass>::get_offset() const
{
    return offset;
}

template <class TripletClass>
size_t SequenceBase<TripletClass>::length() const
{
    return seq.size() - offset;
}

template <class TripletClass>
TripletClass SequenceBase<TripletClass>::get_triplet(size_t i) const throw(OutOfRange)
{
    if (i >= triplets_length())
        throw OutOfRange();

    size_t tripletPosition = calcTripletPosition(i);

    TripletClass t;
    t[0] = seq[tripletPosition];
    t[1] = seq[tripletPosition + 1];
    t[2] = seq[tripletPosition + 2];

    return t;
}

template <class TripletClass>
void SequenceBase<TripletClass>::set_triplet(size_t i, const TripletClass& t) throw(OutOfRange)
{
    if (i >= triplets_length())
        throw OutOfRange();

    size_t tripletPosition = calcTripletPosition(i);

    seq[tripletPosition] = t[0];
    seq[tripletPosition + 1] = t[1];
    seq[tripletPosition + 2] = t[2];
}

void PseudoNucSequence::getNucSequence(NucSequence& n) const
{
    pseudoNucSequence_to_nucSequence(*this, n);
}

template <class TripletClass>
void SequenceBase<TripletClass>::complement(SequenceBase<TripletClass>& output) const
{
    output = *this;
    output.complement();
}

template <class TripletClass>
void SequenceBase<TripletClass>::reverseComplement(SequenceBase<TripletClass>& output) const
{
    output = *this;
    output.reverseComplement();
}

template <class TripletClass>
void SequenceBase<TripletClass>::complement()
{
    const size_t size = length();
    for (size_t i = 0 ; i < size; ++i)
    {
        (*this)[i].complement();
    }
}

template <class TripletClass>
void SequenceBase<TripletClass>::reverseComplement()
{
    const size_t size = length() - 1;
    size_t i = 0;
    size_t j = size;

    for (; i < j; i++, --j)
    {
        const typename TripletClass::NucType aux = (*this)[i];
        (*this)[i] = (*this)[j];
        (*this)[j] = aux;
        (*this)[i].complement();
        (*this)[j].complement();
    }
    if (i == j)
    {
        (*this)[i].complement();
    }
}
}
